using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using SCG.SolarVengeanceEngine;
using SCG.TurboSprite;
using SCG.SolarVengeanceEngine.StarShips;
using SCG.WinForms;

namespace SCG.SolarVengeance
{
    public partial class MapViewer : Control
    {
        //Public members

        //Constructor
        public MapViewer()
        {
            InitializeComponent();
            DoubleBuffered = true;
        }

        //The Game instance whose map should be rendered
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public SVGame Game
        {
            get
            {
                return _game;
            }
            set
            {
                _game = value;
                _bmTerrain = null;
                Invalidate();
            }
        }

        //SpriteSurface so viewport can be drawn
        public SpriteSurface Surface
        {
            get
            {
                return _surface;
            }
            set
            {
                _surface = value;
            }
        }

        //Show all objects, regardless of visibility?
        public bool IgnoreVisibility
        {
            get
            {
                return _showAll;
            }
            set
            {
                _showAll = value;
            }
        }

        //Private members
        private SVGame _game;
        private Bitmap _bmTerrain;
        private int _terrainChange;
        private SpriteSurface _surface;
        private bool _showAll = false;

        //Paint event
        protected override void OnPaint(PaintEventArgs pe)
        {
            try
            {
                //Base rendering if no Game assigned
                if (_game == null)
                {
                    base.OnPaint(pe);
                    pe.Graphics.DrawString("No Game loaded", Font, Brushes.Black, 4, 4);
                    pe.Graphics.DrawRectangle(Pens.White, 0, 0, Width - 1, Height - 1);
                    return;
                }

                //Game object is assigned, map should have valid dimensions
                if (_game.Width <= 0 || _game.Height <= 0)
                {
                    base.OnPaint(pe);
                    pe.Graphics.DrawString("Map does not have valid dimensions", Font, Brushes.Black, 4, 4);
                    pe.Graphics.DrawRectangle(Pens.White, 0, 0, Width - 1, Height - 1);
                    return;
                }

                //avoid looking for current player in brain duels
                if (_game.CurrentPlayer == null)
                    _showAll = true;

                //Determine multiples
                float xMult = (float)Width / _game.Width;
                float yMult = (float)Height / _game.Height;
                PointF pt = new PointF();
                RectangleF rect = new RectangleF(0, 0, xMult, yMult);

                //Pre-render terrain?
                if (_bmTerrain == null || _terrainChange != _game.TerrainChangeKey)
                {
                    _terrainChange = _game.TerrainChangeKey;
                    _bmTerrain = new Bitmap(Width, Height);
                    Graphics grBuffer = Graphics.FromImage(_bmTerrain);
                    using (grBuffer)
                    {
                        //black background
                        grBuffer.Clear(Color.Black);

                        //draw various terrain elements
                        Color clTerrain;
                        for (int x = 0; x < _game.Width; x++)
                            for (int y = 0; y < _game.Height; y++)
                            {
                                clTerrain = Color.Black;
                                switch (_game.GetTerrain(x, y))
                                {
                                    case 'N':
                                    case 'n':
                                        clTerrain = Color.Teal;
                                        break;
                                }
                                if (clTerrain != Color.Black)
                                {
                                    rect.X = x * xMult;
                                    rect.Y = y * yMult;
                                    grBuffer.FillRectangle(new SolidBrush(clTerrain), rect);
                                }
                            }
                    }
                }

                //Render terrain
                pe.Graphics.DrawImage(_bmTerrain, 0, 0);

                //Render StarSystems
                foreach (StarSystem system in _game.StarSystems)
                {
                    pt.X = system.X * xMult;
                    pt.Y = system.Y * yMult;
                    Brush sysBrush = Brushes.Gray;
                    if (system.VisibleToCurrentPlayer || _showAll)
                    {
                        if (system.Owner != null)
                            sysBrush = PenBrushFactory.GetBrush(system.Owner.Color);
                        else
                            sysBrush = Brushes.Silver;
                    }
                    pe.Graphics.FillEllipse(sysBrush, pt.X, pt.Y, 4, 4);

                    //render scanners
                    if (system.OwnedByCurrentPlayer)
                        if (system.Scanners > 1)
                        {
                            int xRadius = (int)(system.Scanners * xMult);
                            int yRadius = (int)(system.Scanners * yMult);
                            pe.Graphics.DrawEllipse(Pens.Teal, pt.X - xRadius, pt.Y - yRadius, xRadius * 2, yRadius * 2);
                        }
                }

                //draw UDOs
                foreach (UDO udo in Game.UDOs)
                    if (udo.VisibleToCurrentPlayer || _showAll)
                    {
                        pt.X = udo.X * xMult;
                        pt.Y = udo.Y * yMult;
                        pe.Graphics.DrawLine(Pens.Beige, pt.X - 2, pt.Y - 2, pt.X + 2, pt.Y + 2);
                        pe.Graphics.DrawLine(Pens.Beige, pt.X + 2, pt.Y - 2, pt.X - 2, pt.Y + 2);
                    }

                //render StarShips
                lock (_game.StarShips)
                {
                    foreach (StarShip ship in _game.StarShips)
                        if (ship.VisibleToCurrentPlayer || _showAll)
                        {
                            pt.X = ship.X * xMult;
                            pt.Y = ship.Y * yMult;
                            pe.Graphics.DrawEllipse(PenBrushFactory.GetPen(ship.Owner.Color), pt.X, pt.Y, 1, 1);

                            //and scanners
                            if (ship.OwnedByCurrentPlayer)
                            {
                                if (ship.ShipType == StarShipType.ScanShip || ship.ShipType == StarShipType.SpyShip)
                                {
                                    int xRadius = (int)(ship.Scanners * xMult);
                                    int yRadius = (int)(ship.Scanners * yMult);
                                    pe.Graphics.DrawEllipse(Pens.Teal, pt.X - xRadius, pt.Y - yRadius, xRadius * 2, yRadius * 2);
                                }
                                else if (ship.ShipType == StarShipType.RemoteViewer)
                                {
                                    RemoteViewer rv = ship as RemoteViewer;
                                    if (rv.ViewX != -1)
                                    {
                                        pt.X = rv.ViewX * xMult;
                                        pt.Y = rv.ViewY * yMult;
                                        int xRadius = (int)(ship.Value * xMult);
                                        int yRadius = (int)(ship.Value * yMult);
                                        pe.Graphics.DrawEllipse(Pens.Teal, pt.X - xRadius, pt.Y - yRadius, xRadius * 2, yRadius * 2);
                                    }
                                }
                            }
                        }
                }

                //Render wormholes
                foreach (Wormhole wh in _game.Wormholes)
                    if (wh.DiscoveredByCurrentPlayer || _showAll)
                    {
                        pt.X = wh.X * xMult;
                        pt.Y = wh.Y * yMult;
                        pe.Graphics.DrawEllipse(Pens.Fuchsia, pt.X - 2, pt.Y - 2, 4, 4);

                        //draw line to destination if it was traveled
                        if (_showAll || wh.TraveledBy[Game.CurrentPlayer])
                        {
                            float destX = wh.DestX * xMult;
                            float destY = wh.DestY * yMult;
                            pe.Graphics.DrawLine(Pens.Olive, pt.X, pt.Y, destX, destY);
                        }
                    }

                //Render BlackHoles
                foreach(BlackHole bh in _game.BlackHoles)
                    if (bh.DiscoveredByCurrentPlayer || _showAll)
                    {
                        pt.X = bh.X * xMult;
                        pt.Y = bh.Y * yMult;
                        pe.Graphics.FillEllipse(Brushes.Black, pt.X - 3, pt.Y - 3, 6, 6);
                        pe.Graphics.DrawEllipse(Pens.Yellow, pt.X - 3, pt.Y - 3, 6, 6);
                    }

                //Render Pulsars
                foreach(Pulsar ps in _game.Pulsars)
                    if (ps.DiscoveredByCurrentPlayer || _showAll)
                    {
                        pt.X = ps.X * xMult;
                        pt.Y = ps.Y * yMult;
                        pe.Graphics.DrawEllipse(Pens.Lime, pt.X - 2, pt.Y - 2, 4, 4);
                    }

                //Render viewport
                if (Surface != null)
                {
                    double viewx = Width / (double)Surface.VirtualWidth;
                    double viewy = Height / (double)Surface.VirtualHeight;
                    int sx = (int)(Surface.OffsetX * viewx);
                    int sy = (int)(Surface.OffsetY * viewy);
                    int sw = (int)(Surface.Width * viewx);
                    int sh = (int)(Surface.Height * viewy);
                    pe.Graphics.DrawRectangle(Pens.Blue, sx, sy, sw, sh);
                }

                //Border
                Pen border = new Pen(ForeColor);
                using (border)
                    pe.Graphics.DrawRectangle(border, 0, 0, Width - 1, Height - 1);
            }
            catch (InvalidOperationException)
            {
                //in case collections were modified
            }
        }

        //Force regeneration of terrain bitmap on resize
        protected override void OnResize(EventArgs e)
        {
            _bmTerrain = null;
            base.OnResize(e);            
        }
    }
}
